package listCacheWindow

import (
	"bytes"
	"fmt"
	"gitee.com/kingzyt/common/log"
	"gitee.com/kingzyt/common/util"
	"sort"
	"strconv"
	"sync"
)

const (
	search_invalid   = -1
	search_contians  = 0
	search_over_head = 1
	search_over_tail = 2
)

var ErrLastIdMiss = fmt.Errorf("lastId is not in current cache")
var ErrLastIdInvalid = fmt.Errorf("lastId is invalid")

type SeqCacheUnit interface {
	GetMainId() int64
	GetId() int64
}

type DefaultIdCacheUnit int64

func (self DefaultIdCacheUnit) GetMainId() int64 {
	return int64(self)
}
func (self DefaultIdCacheUnit) GetId() int64 {
	return int64(self)
}

type DefaultIdDataCacheUnit struct {
	id   int64
	data interface{}
}

func NewDefaultIdDataCacheUnit(id int64, data interface{}) *DefaultIdDataCacheUnit {
	return &DefaultIdDataCacheUnit{id: id, data: data}
}
func (self *DefaultIdDataCacheUnit) GetMainId() int64 {
	return self.id
}
func (self *DefaultIdDataCacheUnit) GetId() int64 {
	return self.id
}
func (self *DefaultIdDataCacheUnit) GetData() interface{} {
	return self.data
}

// ids: desc id list, id is no dup
type ListCacheWindow struct {
	ids []SeqCacheUnit

	hasMore bool
	mutex   sync.Mutex

	extendFactor float32

	hasNew func(cacheNewestId int64) bool

	//获取一个lastId后续的降序排列的队列, 这样整个cache队列就是一个从大到小的
	fetchMoreListCache func(lastId int64, wantCnt int) (appendCache []SeqCacheUnit)
}

// extendFactor should be >= 1.0
func NewListCacheWindow(extendFactor float32, fetchMoreListCache func(lastId int64, wantCnt int) (appendCache []SeqCacheUnit), hasNew func(cacheNewestId int64) bool) *ListCacheWindow {
	if extendFactor < 1.0 {
		extendFactor = 1.0
	}
	rlt := &ListCacheWindow{extendFactor: extendFactor, fetchMoreListCache: fetchMoreListCache, hasNew: hasNew}
	rlt.reset()
	return rlt
}
func (self *ListCacheWindow) Size() int {
	self.mutex.Lock()
	defer self.mutex.Unlock()
	return len(self.ids)
}
func (self *ListCacheWindow) reset() {
	self.ids = []SeqCacheUnit{}
	self.hasMore = true
}
func (self *ListCacheWindow) Reset() {
	self.mutex.Lock()
	defer self.mutex.Unlock()
	self.reset()
}
func (self *ListCacheWindow) fetchMore(wantCnt int) {
	if !self.hasMore {
		return
	}

	extendCnt := int(float32(wantCnt) * self.extendFactor)
	lastId := int64(0)
	if len(self.ids) > 0 {
		lastId = self.ids[len(self.ids)-1].GetId()
	}
	appendIds := self.fetchMoreListCache(lastId, extendCnt+1)

	hasMore := len(appendIds) > extendCnt
	if hasMore {
		appendIds = appendIds[:extendCnt]
	}

	self.ids = append(self.ids, appendIds...)
	self.hasMore = hasMore
}

/*
这里仅仅为了配合recommendMsg这样的频繁变动id而作的妥协，
id变化为只增不减，
ids是一次性获取，到下一次cache失效前不再更新，保证一次快照的id稳定
这里的id修正就是为了最大限度将上一次快照的id与最新快照的id对列进行兼容，只要在范围内的都可以取最接近的大值为结果
*/
func (self *ListCacheWindow) roundIdToRange(lastId int64) int64 {
	if lastId <= 0 {
		return lastId // 无效或重新获取
	}

	if len(self.ids) <= 0 {
		return lastId //原路径，返回错误让client重新取
	}
	if lastId > self.ids[0].GetId() {
		return lastId //原路径，直接错误
	}
	tailId := self.ids[len(self.ids)-1].GetId()
	if lastId < tailId {
		return tailId //修正为最后一个id，这样就返回空队列
	}

	for i := 0; i < len(self.ids); i++ {
		id := self.ids[i].GetId()
		if lastId == id {
			return id
		}
		// 这里已经从逻辑上保证，lastid必然是落在首尾id之间的某一个区间中，不是定点id上，一定会在尾id这个循环之前return；本算法已经使用大样本测试通过
		if id > lastId && lastId > self.ids[i+1].GetId() {
			return id
		}
	}
	return -1
}

func (self *ListCacheWindow) GetWithRoundIdToRange(lastId int64, wantCnt int) (rltIds []SeqCacheUnit, err error) {
	self.mutex.Lock()
	defer self.mutex.Unlock()
	return self.getCore(self.roundIdToRange(lastId), wantCnt)
}

func (self *ListCacheWindow) Get(lastId int64, wantCnt int) (rltIds []SeqCacheUnit, err error) {
	self.mutex.Lock()
	defer self.mutex.Unlock()
	return self.getCore(lastId, wantCnt)
}

/*
所有的获取, 要么直接获取最新的(lastId==0), 要么从cache中已有的一个点开始获取后续(lastId在cache中), 不允许在cache的范围以外任意指定接续点
*/
func (self *ListCacheWindow) getCore(lastId int64, wantCnt int) (rltIds []SeqCacheUnit, err error) {
	if lastId < 0 {
		return nil, ErrLastIdInvalid
	}

	getLastIdx := func() (idx int, searchRltCode int) {
		if len(self.ids) > 0 {
			if lastId > self.ids[0].GetId() {
				return 0, search_over_head
			}
			if lastId < self.ids[len(self.ids)-1].GetId() {
				return 0, search_over_tail
			}
			for i := len(self.ids) - 1; i >= 0; i-- {
				if self.ids[i].GetId() == lastId {
					return i, search_contians
				}
			}
			return 0, search_invalid
		} else {
			return 0, search_over_tail
		}
	}

	returnIds := func(beginIdx int) []SeqCacheUnit {
		if beginIdx >= len(self.ids) {
			return []SeqCacheUnit{}
		}

		ids := self.ids[beginIdx:]
		curLen := len(ids)
		if curLen > wantCnt {
			return ids[:wantCnt]
		} else {
			return ids
		}
	}

	isFirstPage := lastId == 0
	if isFirstPage {
		oldIds := self.ids
		oldHasMore := self.hasMore
		reseted := false

		if len(self.ids) > 0 {
			if self.hasNew(self.ids[0].GetId()) {
				self.reset()
				reseted = true
			}
		} else {
			if self.hasNew(0) {
				self.reset()
				reseted = true
			}
		}
		if wantCnt > len(self.ids) {
			self.fetchMore(wantCnt)
		}

		if reseted && len(self.ids) > 0 {
			tailId := self.ids[len(self.ids)-1]
			for i := 0; i < len(oldIds); i++ {
				if oldIds[i].GetId() == tailId.GetId() {
					if len(oldIds) > i+1 {
						self.ids = append(self.ids, oldIds[i+1:]...)
						self.hasMore = oldHasMore

						/*
							// test traces
							var einfo bytes.Buffer
							for _, id := range self.ids {
								einfo.WriteString(strconv.FormatInt(id.GetId(), 10) + ",")
							}
							log.Info("", log.Test_1, "ids append old ids: %s", string(einfo.Bytes()))
						*/
					}
					break
				}
			}
		}

		return returnIds(0), nil
	} else {
		lastIdIdx, searchRltCode := getLastIdx()
		switch searchRltCode {
		default:
			return nil, fmt.Errorf("searchRltCode is invalid")
		case search_invalid:
			return nil, ErrLastIdInvalid
		case search_contians:
			beginIdx := lastIdIdx + 1
			tailLastIdIds := self.ids[beginIdx:]
			if len(tailLastIdIds) < wantCnt {
				self.fetchMore(wantCnt)
			}
			return returnIds(beginIdx), nil
		case search_over_head, search_over_tail:
			var einfo bytes.Buffer
			if len(self.ids) > 10 {
				for _, id := range self.ids[:5] {
					einfo.WriteString(strconv.FormatInt(id.GetId(), 10) + ",")
				}
				einfo.WriteString("...")
				for _, id := range self.ids[len(self.ids)-5:] {
					einfo.WriteString(strconv.FormatInt(id.GetId(), 10) + ",")
				}
			} else {
				for _, id := range self.ids {
					einfo.WriteString(strconv.FormatInt(id.GetId(), 10) + ",")
				}
			}
			log.Error("", "lastId is not in current cache: lastId:%d, cur ids:len %d, [%s]", lastId, len(self.ids), string(einfo.Bytes()))

			return nil, ErrLastIdMiss
		}
	}
}

func (self *ListCacheWindow) InsertHeadUnit(unit SeqCacheUnit) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	//check dup
	for _, v := range self.ids {
		if v.GetId() == unit.GetId() {
			return
		}
	}

	tmp := self.ids
	self.ids = append([]SeqCacheUnit(nil), unit)
	self.ids = append(self.ids, tmp...)

	sort.SliceStable(self.ids, func(i, j int) bool {
		return self.ids[i].GetId() > self.ids[j].GetId()
	})
}
func (self *ListCacheWindow) DeleteUnit(id int64) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	rlt, elemDeleted := util.SliceMultiDelByReflect(self.ids, func(idx int, elem interface{}) bool {
		return elem.(SeqCacheUnit).GetId() == id
	})
	if elemDeleted {
		self.ids = rlt.([]SeqCacheUnit)
	}
}
func (self *ListCacheWindow) IteratorUnits(proc func(unit SeqCacheUnit) bool) {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	for _, v := range self.ids {
		if !proc(v) {
			return
		}
	}
}
func (self *ListCacheWindow) ExchangeUnits(id0, id1 int64, procReturnNew func(u0 SeqCacheUnit, u1 SeqCacheUnit) (newU0 SeqCacheUnit, newU1 SeqCacheUnit)) error {
	self.mutex.Lock()
	defer self.mutex.Unlock()

	var oldU0, oldU1 SeqCacheUnit
	var idxU0, idxU1 int
	for i, v := range self.ids {
		if v.GetId() == id0 {
			oldU0 = v
			idxU0 = i
		} else if v.GetId() == id1 {
			oldU1 = v
			idxU1 = i
		}

		if oldU0 != nil && oldU1 != nil {
			break
		}
	}
	if oldU0 == nil || oldU1 == nil {
		return fmt.Errorf("didnot find unit")
	}

	newU0, newU1 := procReturnNew(oldU0, oldU1)
	if newU0.GetId() != oldU0.GetId() || newU1.GetId() != oldU1.GetId() {
		return fmt.Errorf("must not change unit id")
	}
	if newU0 == oldU0 || newU1 == oldU1 || newU0 == oldU1 || newU1 == oldU0 {
		return fmt.Errorf("must return new values for replacing")
	}

	self.ids[idxU0] = newU0
	self.ids[idxU1] = newU1
	return nil
}
